/****

    activequant - activestocks.eu

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

	
	contact  : contact@activestocks.eu
    homepage : http://www.activestocks.eu

****/
package org.activequant.dao.hibernate;

import java.lang.reflect.Array;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.activequant.dao.IDaoSupport;
import org.activequant.util.exceptions.DaoException;
import org.activequant.util.tools.ArrayUtils;
import org.hibernate.CacheMode;
import org.hibernate.HibernateException;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * An abstract class. DaoSupportBase&lt;T&gt; extends HibernateDaoSupport implements IDaoSupport&lt;T&gt;.<br/>
 * Holds the following associated variable:
 * <ul>
 * <li>clazz(Class&lt;T&gt;)</li>
 * </ul>
 * Holds the following inherited variable:
 * <ul>
 * <li>hibernateTemplate(HibernateTemplate)</li>
 * </ul>
 * <br>
 * <b>History:</b><br>
 *  - [21.05.2007] Created (Erik Nijkamp)<br>
 *  - [28.09.2007] Moved to new domain model (Erik Nijkamp)<br>
 *  - [24.10.2007] Added batch update (Erik Nijkamp)<br>
 *  - [18.11.2007] Replaced deleteAll() with complex method (Erik Nijkamp)<br>
 *  - [19.11.2007] Fixed findByExample (Ulrich Staudinger)<br>
 *  - [16.08.2008] Fixed Java6 no longer accepts brute cast from long to integer (Wessel de Roode)<br>
 *  @author Erik Nijkamp
 *  @author Ulrich Staudinger
 */
public abstract class DaoSupportBase<T> extends HibernateDaoSupport implements IDaoSupport<T> {
	/**
	 * protected Class&lt;T&gt; clazz;
	 */
	protected Class<T> clazz;
	/**
	 * Constructs a DaoSupportBase&lt;T&gt;(extends HibernateDaoSupport implements IDaoSupport&lt;T&gt;) using the given clazz(Class&lt;T&gt;)
	 * to set its associated clazz(Class&lt;T&gt;).
	 * @param clazz
	 */
	protected DaoSupportBase(Class<T> clazz) {
		this.clazz = clazz;		
	}
	/**
	 * Constructs a DaoSupportBase&lt;T&gt;(extends HibernateDaoSupport implements IDaoSupport&lt;T&gt;) using the given clazz(Class&lt;T&gt;)
	 * to set its associated clazz(Class&lt;T&gt;).<br/>
	 * Set the Hibernate SessionFactory to be used by this DAO SupportBase. Will automatically create a HibernateTemplate for the given sessionFactory(SessionFactory).
	 * @param clazz
	 * @param sessionFactory
	 */
	protected DaoSupportBase(Class<T> clazz, SessionFactory sessionFactory) {
		this(clazz);
		setSessionFactory(sessionFactory);
	}
	/**
	 * invokes <code>getHibernateTemplate().delete(entity)</code> to delete the given entity(T)
	 */
	public void delete(T entity) {
		try {
			getHibernateTemplate().delete(entity);
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * returns the persistent instance(T) (of the associated clazz(Class&lt;T&gt;)) identified by the given id(long).<br/>
	 * invokes and returns <code>(T)getHibernateTemplate().get(clazz, id)</code>
	 */
	@SuppressWarnings("unchecked")
	public T find(long id) {
		try {			
			return (T) getHibernateTemplate().get(clazz, id);
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * returns all persistent instances(T[]) (of the associated clazz(Class&lt;T&gt;)) of the given entity(T) example
	 */
	@SuppressWarnings("unchecked")
	public T[] findAllByExample(T entity) {
		try {
			List list = (List) getHibernateTemplate().findByExample(entity);
			return (T[]) ArrayUtils.asArray(list, clazz);
		} catch (Exception e) {
			throw new DaoException(e);
		}		
	}
    /**
     * returns the (there should't be more than one) persistent instance(T) of the given entity(T) example or null if none is found.
     */
	@SuppressWarnings("unchecked")
	public T findByExample(T entity) {
		try {
			List list = getHibernateTemplate().findByExample(entity);
			if(list == null) {
				// no result found
				return null;
			}
			
			// return result
			if(list.size() == 1) {
				entity = (T) list.get(0);
				return entity;
			} else if(list.size() > 1) {
				throw new DaoException("Ambigous results");
			}
			
			// no result found
			return null;
			
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * returns all persistent instances(T[]) of the associated clazz(Class&lt;T&gt;)
	 */
	@SuppressWarnings("unchecked")
	public T[] findAll() {
		try {
			List<T> list = getHibernateTemplate().loadAll(clazz);
	        T[] array = (T[]) Array.newInstance(clazz, list.size());
	        return list.toArray(array);
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * Saves or updates the given persistent entity(T) instance, according to its id (matching the configured "unsaved-value"?).<br/>
	 * Associates the instance with the current Hibernate Session.<br/>
	 * Then it returns the same entity(T) instance.
	 */
	public T update(T entity) {
		try {
			getHibernateTemplate().saveOrUpdate(entity);
			return entity;
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * Saves or updates all given persistent entities(T...) instances, according to its id (matching the configured "unsaved-value"?).<br/>
	 * Associates the instances with the current Hibernate Session.<br/>
	 * Then it returns the same entities(T...) instances.
	 */
	public T[] update(T... entities) throws DaoException {
		try {
			update(ArrayUtils.asList(entities));
			return entities;
		} catch (Exception e) {
			throw new DaoException(e);
		}	
	}
    /**
     * Saves or updates all given persistent entities(List&lt;T&gt;) instances, according to its id (matching the configured "unsaved-value"?).<br/>
	 * Associates the instances with the current Hibernate Session.<br/>
	 * Then it returns the same entities(List&lt;T&gt;) instances.
     */
	public List<T> update(List<T> entities) throws DaoException {
		try {
			getHibernateTemplate().saveOrUpdateAll(entities);
			return entities;
		} catch (Exception e) {
			throw new DaoException(e);
		}		
	}
	/**
	 * Delete all given persistent entities(T...) instances. 
	 */
	public void delete(T... entities) {
		delete(ArrayUtils.asList(entities));
	}
	
    /**
     * Generic method used to remove a set of objects.<br/>
     * Delete all given persistent entities(List&lt;T&gt;) instances. 
     * @param entity
     * @throws DaoException
     */
    public void delete(List<T> entities) throws DaoException {
		try {
			getHibernateTemplate().deleteAll(new ArrayList<T>(entities));
		} catch (Exception e) {
			throw new DaoException(e);
		}   	
    }
	/**
	 * protected final int BATCH_SIZE = 100;
	 */
	protected final int BATCH_SIZE = 100;
	/**
	 * Deletes all persistent instances of the associated clazz(Class&lt;T&gt;).<br/>
	 * It deletes/flushes the records from database in batches of BATCH_SIZE(100) at a time, using a HibernateCallback.
	 */
	public void deleteAll() {
		try {
			/* delete subsets by hand cause:
			 * - loadAll() throws OutOfMemoryException
			 * - bulk-delete ignores cascading deletes
			 */
			getHibernateTemplate().execute(new HibernateCallback() { 
				public Object doInHibernate(Session session) throws HibernateException, SQLException {
					ScrollableResults objects = session.createCriteria(clazz).setCacheMode(CacheMode.IGNORE).scroll(ScrollMode.FORWARD_ONLY);
					int count = 0;
					while(objects.next()) {
				    	Object entity = objects.get(0);
			        	session.delete(entity);
				    	if(++count % BATCH_SIZE == 0) {
				        	// flush a batch of updates and release memory
				    		session.flush();
				        	session.clear();
				    	}
					}
					return null;
				}
			});
		} catch (Exception e) {
			throw new DaoException(e);
		}
	}
	/**
	 * Returns the number(int) of persistent instances of the associated clazz(Class&lt;T&gt;)
	 */
    @SuppressWarnings("unchecked")
	public int count() {
		try {
	        List<Long> list = getHibernateTemplate().find("SELECT count(*) FROM " + clazz.getName());
	        Integer count = list.get(0).intValue();
	        return count.intValue();
		} catch (Exception e) {
			throw new DaoException(e);
		}
    }

}
